#include "nano_db.h"
#include <string.h>
#include <stdlib.h>

struct ndb_ns_t skynetworks_ns = {"config", "http://skynetworks.com.cn/lte/bts/config/2.0.0"};
struct ndb_ns_t router_ns = {"router", "http://skynetworks.com.cn/lte/bts/router/2.0.0"};
struct ndb_ns_t equipment_ns = {"equipment", "http://skynetworks.com.cn/lte/bts/equipment/2.0.0"};
struct ndb_ns_t interface_ns = {"interface", "http://skynetworks.com.cn/lte/bts/interfaces/2.0.0"};
struct ndb_ns_t system_ns = {"system", "http://skynetworks.com.cn/lte/bts/system/2.0.0"};
struct ndb_ns_t traffic_man_ns = {"traffic-management", "http://skynetworks.com.cn/lte/bts/traffic-management/2.0.0"};
struct ndb_ns_t services_ns = {"services", "http://skynetworks.com.cn/lte/bts/services/2.0.0"};

/*
 * ndb_create
 *
 */
int ndb_create(struct ndb_t* db, const char* file)
{
    db->fname = file;
    db->xmldoc = xmlReadFile(db->fname, "UTF-8", XML_PARSE_RECOVER);
    if (db->xmldoc == NULL) 
    {
        return -1;
    }

    db->root = xmlDocGetRootElement(db->xmldoc); 
    if (db->root == NULL)
    {
        xmlFreeDoc(db->xmldoc);
        return -1;
    }

    return 0;
}

/*
 * ndb_save/ndb_save_as
 *
 */
int ndb_save_as(struct ndb_t* db, const char* file)
{
    if (db->xmldoc == NULL)
        return -1;

    xmlSaveFile(file, db->xmldoc);

    return 0;
}

int ndb_save(struct ndb_t* db)
{
    return ndb_save_as(db, db->fname);
}

/*
 * ndb_destory
 *
 */
int ndb_destory(struct ndb_t* db)
{
    if (db->xmldoc == NULL)
        return -1;
    
    xmlFreeDoc(db->xmldoc);
    memset(db, 0, sizeof(struct ndb_t));

    return 0;
}

/*
 * ndb_create_context
 *
 */
int ndb_create_context(struct ndb_t* db, struct ndb_ns_t* ns, struct ndb_ctx_t* ctx)
{
    if (db->xmldoc == NULL || ctx == NULL)
        return -1;
    
    ctx->xpath_ctx = xmlXPathNewContext(db->xmldoc);

    /*if (xmlXPathRegisterNs(ctx->xpath_ctx, (xmlChar*)root__ns->name, (xmlChar*)root__ns->href)!= 0)
    {
        xmlXPathFreeContext(ctx->xpath_ctx);
        return -1;        
    }*/

    if ((ns != NULL) && 
        (xmlXPathRegisterNs(ctx->xpath_ctx, (xmlChar*)ns->name, (xmlChar*)ns->href) != 0))
    {
        /* xmlXPathFreeContext(ctx->xpath_ctx); 
        return -1; */
        printf("ndb: can't register namespace %s=%s\n", ns->name, ns->href);
    }
   
    return 0;
}

/*
 * ndb_register_namespaces
 *
 */
int ndb_register_namespaces(struct ndb_ctx_t* ctx, struct ndb_ns_t* nss[], int nn)
{
    int i;

    for (i = 0; i < nn; i++)
    {
        if (xmlXPathRegisterNs(ctx->xpath_ctx, (xmlChar*)nss[i]->name, (xmlChar*)nss[i]->href)!= 0)
        {
            return -1;        
        }
    }
   
    return 0; 
}

/*
 * ndb_destory_context
 *
 */
int ndb_destory_context(struct ndb_ctx_t* ctx)
{
    if (ctx->xpath_ctx == NULL)
        return -1;

    xmlXPathFreeContext(ctx->xpath_ctx);

    return 0;
}

/*
 * ndb_query
 *
 */
int ndb_query(struct ndb_t* db, struct ndb_ctx_t* ctx, const char* xpath, struct ndb_tabset_t* set)
{
    if (ctx->xpath_ctx == NULL)
        return -1;

    set->xpobj = xmlXPathEvalExpression((xmlChar*)xpath, ctx->xpath_ctx);
    if (set->xpobj == NULL)
        return -1;

    if (xmlXPathNodeSetIsEmpty(set->xpobj->nodesetval))
    {
        return -1;
    }

    set->tbl_nr = set->xpobj->nodesetval->nodeNr;

    return set->xpobj->nodesetval->nodeNr;
}

/*
 * ndb_get_tab_nr
 *
 */
int ndb_get_tab_nr(struct ndb_tabset_t* set)
{
    if (xmlXPathNodeSetIsEmpty(set->xpobj->nodesetval))
        return 0;
    else
        return set->xpobj->nodesetval->nodeNr;
}

ndb_elem_ptr ndb_get_first(struct ndb_tabset_t* set)
{
    if (!ndb_get_tab_nr(set))
        return NULL;

    return (ndb_elem_ptr)set->xpobj->nodesetval->nodeTab[0];
}

/*
 * ndb_add_child
 *
 */
ndb_elem_ptr ndb_add_child(ndb_elem_ptr p, struct ndb_ns_t* ns, const char* name, const char* val)
{
    ndb_elem_ptr elem = NULL;

    elem = (ndb_elem_ptr)xmlNewChild((xmlNodePtr)p, NULL, (xmlChar*)name, (xmlChar*)val);

    if (ns != NULL && elem != NULL)
        xmlNewNs((xmlNodePtr)elem, (xmlChar*)ns->name, (xmlChar*)ns->href);

    return elem;
}

/*
 * ndb_add_sibling
 *
 */
ndb_elem_ptr ndb_add_sibling(ndb_elem_ptr p, struct ndb_ns_t* ns, const char* name, const char* val)
{
    ndb_elem_ptr elem = NULL;

    elem = (ndb_elem_ptr)xmlNewNode(NULL, (xmlChar*)name);

    if (val != NULL)
        xmlNodeSetContent(elem, (xmlChar*)val);

    xmlAddSibling(p, elem);

    if (ns != NULL && elem != NULL)
        xmlNewNs((xmlNodePtr)elem, (xmlChar*)ns->name, (xmlChar*)ns->href);

    return elem;
}

/*
 * ndb_set_prop
 *
 */
int ndb_set_prop(ndb_elem_ptr elem, const char* propname, const char* popval)
{
    if (xmlSetProp(elem, (xmlChar*)propname, (xmlChar*)popval) == NULL)
        return -1;

    return 0;    
}

/*
 * ndb_get_prop
 *
 */
const char* ndb_get_prop(ndb_elem_ptr elem, const char* propname)
{
    return (char*)xmlGetProp(elem, (xmlChar*)propname);
}

/*
 * ndb_has_prop
 *
 */
int ndb_has_prop(ndb_elem_ptr elem, const char* propname)
{
    if (xmlHasProp(elem, (xmlChar*)propname) == NULL)
        return -1;

    return 0;    
}

int ndb_has_prop_and_value(ndb_elem_ptr elem, const char* propname, const char* popval)
{
    if (ndb_has_prop(elem, propname) == 0) {
        if (strcmp(ndb_get_prop(elem, propname), popval) != 0) {
            return -1;
        } 
        
        return 0;     
    }


    return -1;
}

const char* ndb_get_name(ndb_elem_ptr elem)
{
    return (char*)elem->name;
}

const char* ndb_get_value_as_str(ndb_elem_ptr elem)
{
    return (char*)xmlNodeGetContent(elem);
}

int32_t ndb_get_value_as_int32(ndb_elem_ptr elem)
{
    return atoi(ndb_get_value_as_str(elem));
}

uint32_t ndb_get_value_as_u32(ndb_elem_ptr elem)
{
    return (uint32_t)atol(ndb_get_value_as_str(elem));
}

float ndb_get_value_as_float(ndb_elem_ptr elem)
{
    float res = 0.0;

    const char* value = ndb_get_value_as_str(elem);

    if (value != NULL)
        sscanf(value, "%f", &res);

    return res;    
}

uint16_t ndb_get_value_as_u16(ndb_elem_ptr elem)
{
    return (uint16_t)ndb_get_value_as_u32(elem);
}

int16_t ndb_get_value_as_int16(ndb_elem_ptr elem)
{
    return (int16_t)ndb_get_value_as_int32(elem);
}

uint8_t ndb_get_value_as_u8(ndb_elem_ptr elem)
{
    return (uint8_t)ndb_get_value_as_u32(elem);
}

int8_t ndb_get_value_as_int8(ndb_elem_ptr elem)
{
    return (int8_t)ndb_get_value_as_int32(elem);
}

/*
 * ndb_get_value
 *
 */
int ndb_get_value(ndb_elem_ptr elem, struct ndb_value_t* value)
{
    switch (value->type) {
        case C_STR:
            value->val.s = (char*)ndb_get_value_as_str(elem);
            break;

        case C_UINT16:
            value->val.u16 = ndb_get_value_as_u16(elem);
            break;

        case C_UINT32:
            value->val.u32 = ndb_get_value_as_u32(elem);
            break;
            
        case C_SINT16:
            value->val.s16 = ndb_get_value_as_int16(elem);
            break;

        case C_SINT32:
            value->val.s32 = ndb_get_value_as_int32(elem);
            break;
        
        case C_UINT8: 
            value->val.u8 = ndb_get_value_as_u8(elem);
            break;
            
        case C_SINT8:  
            value->val.s8 = ndb_get_value_as_int8(elem);
            break;
            
        case C_FLOAT:
            value->val.f = ndb_get_value_as_float(elem);
            break;  

        default:
            return -1;
    }

    return 0;     
}

void ndb_set_str(ndb_elem_ptr elem, const char* value)
{
    xmlNodeSetContent(elem, (xmlChar*)value);
}

void ndb_set_int32(ndb_elem_ptr elem, int val)
{
    char str[16] = {0};

    sprintf(str, "%d", val);
    ndb_set_str(elem, str);    
}

void ndb_set_u32(ndb_elem_ptr elem, uint32_t val)
{
    char str[16] = {0};

    sprintf(str, "%u", val);
    ndb_set_str(elem, str);      
}

void ndb_set_u16(ndb_elem_ptr elem, uint16_t val)
{
    ndb_set_u32(elem, val);
}

void ndb_set_int16(ndb_elem_ptr elem, int16_t val)
{
    ndb_set_int32(elem, val);
}

void ndb_set_u8(ndb_elem_ptr elem, uint8_t val)
{
    ndb_set_u32(elem, val);
}

void ndb_set_int8(ndb_elem_ptr elem, int8_t val)
{
    ndb_set_int32(elem, val);
}

void ndb_set_float(ndb_elem_ptr elem, float val)
{
    char str[16] = {0};

    sprintf(str, "%f", val);
    ndb_set_str(elem, str); 
}

/*
 * ndb_set_value
 *
 */
int ndb_set_value(ndb_elem_ptr elem, struct ndb_value_t* value)
{
    switch (value->type) {
        case C_STR:
            ndb_set_str(elem, value->val.s);
            break;

        case C_UINT16:
            ndb_set_u16(elem, value->val.u16);
            break;

        case C_UINT32:
            ndb_set_u32(elem, value->val.u32);
            break;
            
        case C_SINT16:
            ndb_set_int16(elem, value->val.s16);
            break;
            
        case C_SINT32:
            ndb_set_int32(elem, value->val.s32);
            break;

        case C_UINT8: 
            ndb_set_u8(elem, value->val.u8);
            break;
            
        case C_SINT8:
             ndb_set_int8(elem, value->val.s8);
            break;
           
        case C_FLOAT:
            ndb_set_float(elem, value->val.f);
            break;  

        default:
            return -1;
    }

    return 0;    
}

const char* ndb_path(struct ndb_tabset_t* set)
{
    return (char*)xmlGetNodePath(set->xpobj->nodesetval->nodeTab[0]);
}

/*
 * ndbc_attach
 *
 */
int ndbc_attach(struct ndb_cursor_t* c, struct ndb_tabset_t* set)
{
    if (xmlXPathNodeSetIsEmpty(set->xpobj->nodesetval))
        return -1;

    memset(c, 0, sizeof(struct ndb_cursor_t));

    c->anchor = c->node = set->xpobj->nodesetval->nodeTab[0];
    c->prev = c->anchor;

    return 0;
}

/*
 * get_elem_set
 *
 */
ndb_elset_ptr get_elem_set(struct ndb_tabset_t* set)
{
    if (!ndb_get_tab_nr(set))
        return NULL;

    return (ndb_elset_ptr)set->xpobj->nodesetval;    
}

/*
 * ndb_compare:
 *    compare elem's name and param 'name', 
 * 
 */
int ndb_compare(ndb_elem_ptr elem, const char* name)
{
    if (strcmp(ndb_get_name(elem), name) != 0) {
        return -1;
    }

    return 0; 
}

/*
 * ndb_compare_and_set:
 *     compare elem's name and param 'name', 
 */
int ndb_compare_and_set(ndb_elem_ptr elem, const char* name, struct ndb_value_t* value)
{
    if (strcmp(ndb_get_name(elem), name) != 0) {
        return -1;
    }

    return ndb_set_value(elem, value);
}

/*
 * ndb_compare_and_get:
 *    compare elem's name and param 'name',  
 */
int ndb_compare_and_get(ndb_elem_ptr elem, const char* name, struct ndb_value_t* value)
{
    if (strcmp(ndb_get_name(elem), name) != 0) {
        return -1;
    }
    
    return ndb_get_value(elem, value);
}

/*
 * ndb_test_prop_and_set
 *
 */
int ndb_test_prop_and_set(ndb_elem_ptr elem, const char* propname, const char* propval)
{
    if (ndb_has_prop(elem, propname) == -1)
        return -1;

    return ndb_set_prop(elem, propname, propval);
}

/*
 * ndb_test_prop_and_get
 *
 */
const char* ndb_test_prop_and_get(ndb_elem_ptr elem, const char* propname)
{
    if (ndb_has_prop(elem, propname) == -1)
        return NULL;
   
   return ndb_get_prop(elem, propname);
}

int ndbc_next_table(struct ndb_cursor_t* c, struct ndb_tabset_t* set)
{
    if (xmlXPathNodeSetIsEmpty(set->xpobj->nodesetval))
        return -1;

    if (c->tbl_idx >= set->tbl_nr)
        return -1;

    c->anchor = c->node = set->xpobj->nodesetval->nodeTab[++c->tbl_idx];
    c->prev = c->anchor;

    return 0;
}

int ndbc_more_table(struct ndb_cursor_t* c, struct ndb_tabset_t* set)
{
    return (c->tbl_idx < set->tbl_nr);
}

const char* ndbc_path(struct ndb_cursor_t* c)
{
    return (char*)xmlGetNodePath(c->node);
}

int ndbc_empty(struct ndb_cursor_t* c)
{
    return xmlIsBlankNode(c->node);
}

/*
 * ndbc_move
 *
 */
int ndbc_move(struct ndb_cursor_t* c)
{
    if (c->node == NULL)
        return -1;

    do {
        c->prev = c->node;
        c->node = c->node->next;
    } while (c->node != NULL && xmlNodeIsText(c->node));

    return (c->node != NULL)? 0: -1;
}

/*
 * ndbc_down
 *
 */
int ndbc_down(struct ndb_cursor_t* c)
{
    if (c->node == NULL || c->node->children == NULL)
        return -1;

    c->node = c->node->children;
    
    while (c->node != NULL && xmlNodeIsText(c->node))
    {
        c->node = c->node->next;
    }

    c->prev = c->node;
    return (c->node != NULL)? 0: -1;
}

/*
 * ndbc_up
 *
 */
int ndbc_up(struct ndb_cursor_t* c)
{
    if (c->prev == NULL || c->prev->parent == NULL)
        return -1;

    if (c->prev == c->anchor) {
        return -1;
    }

    c->node = c->prev->parent;
    c->prev = c->node;

    return 0;
}

/*
 * ndbc_get_name
 *
 */
const char* ndbc_get_name(struct ndb_cursor_t* c)
{
    return (char*)c->node->name;
}

const char* ndbc_get_prop(struct ndb_cursor_t* c, const char* propname)
{
     return (char*)xmlGetProp(c->node, (xmlChar*)propname);
}

int ndbc_set_prop(struct ndb_cursor_t* c, const char* propname, const char* popval)
{
    return ndb_set_prop(c->node, propname, popval);
}

int ndbc_has_prop(struct ndb_cursor_t* c, const char* propname)
{
    return ndb_has_prop(c->node, propname);
}

void ndbc_set_str(struct ndb_cursor_t* c, const char* value)
{
    xmlNodeSetContent(c->node, (xmlChar*)value);
}

void ndbc_set_int32(struct ndb_cursor_t* c, int val)
{
    ndb_set_int32((ndb_elem_ptr)c->node, val);
}

void ndbc_set_u32(struct ndb_cursor_t* c, unsigned int val)
{
    ndb_set_u32((ndb_elem_ptr)c->node, val);
}

void ndbc_set_u16(struct ndb_cursor_t* c, uint16_t val)
{
    ndb_set_u16((ndb_elem_ptr)c->node, val);
}

void ndbc_set_int16(struct ndb_cursor_t* c, int16_t val)
{
    ndb_set_int16((ndb_elem_ptr)c->node, val);
}

void ndbc_set_u8(struct ndb_cursor_t* c, uint8_t val)
{
    ndb_set_u8((ndb_elem_ptr)c->node, val);
}

void ndbc_set_int8(struct ndb_cursor_t* c, int8_t val)
{
    ndb_set_int8((ndb_elem_ptr)c->node, val);
}

void ndbc_set_float(struct ndb_cursor_t* c, float val)
{
    ndb_set_float((ndb_elem_ptr)c->node, val);   
}

const char* ndbc_get_value_as_str(struct ndb_cursor_t* c)
{
     return (char*)xmlNodeGetContent(c->node);
}

int ndbc_get_value_as_int32(struct ndb_cursor_t* c)
{
    return atoi(ndbc_get_value_as_str(c));
}

unsigned int ndbc_get_value_as_u32(struct ndb_cursor_t* c)
{
    return (unsigned int)atol(ndbc_get_value_as_str(c));
}

float ndbc_get_value_as_float(struct ndb_cursor_t* c)
{
    return ndb_get_value_as_float((ndb_elem_ptr)c->node);
}

uint16_t ndbc_get_value_as_u16(struct ndb_cursor_t* c)
{
    return ndb_get_value_as_u16((ndb_elem_ptr)c->node);
}

int16_t ndbc_get_value_as_int16(struct ndb_cursor_t* c)
{
    return ndb_get_value_as_int16((ndb_elem_ptr)c->node);
}

uint8_t ndbc_get_value_as_u8(struct ndb_cursor_t* c)
{
    return ndb_get_value_as_u8((ndb_elem_ptr)c->node);
}

int8_t ndbc_get_value_as_int8(struct ndb_cursor_t* c)
{
    return ndb_get_value_as_int8((ndb_elem_ptr)c->node);
}

int ndbc_get_value(struct ndb_cursor_t* c, struct ndb_value_t* value)
{
    return ndb_get_value(c->node, value);
}

int ndbc_set_value(struct ndb_cursor_t* c, struct ndb_value_t* value)
{
    return ndb_set_value(c->node, value);
}

static unsigned long fill_byte(void* p, unsigned long addr, uint8_t val)
{
    uint8_t* vp = (uint8_t*)((unsigned long)p + addr);

    *vp = val;

    //unsigned long pad = 0;
    //printf("    addr = %lu, pad = %lu\n", addr, pad);
    return (addr + 1);
}

static inline unsigned long fill_halfword(void* p, unsigned long addr, uint16_t val, int align)
{
    unsigned long pad = 0;
    if (align && addr%sizeof(uint32_t)) 
        pad = 2 - addr%sizeof(uint32_t);
    
    uint16_t* vp = (uint16_t*)((unsigned long)p + addr + pad);

    *vp = val;

    //printf("    addr = %lu, pad = %lu\n", addr, pad);
    return (addr + pad + 2);    
}

static inline unsigned long fill_word(void* p, unsigned long addr, uint32_t val, int align)
{
    unsigned long pad = 0;
    if (align && addr%sizeof(uint32_t)) 
        pad = 4 - addr%sizeof(uint32_t);

    uint32_t* vp = (uint32_t*)((unsigned long)p + addr + pad);

    *vp = val;
    
    //printf("    addr = %lu, pad = %lu\n", addr, pad);
    return (addr + pad + 4);     
}

void* ndb_get_container(ndb_elem_ptr container, void* buf)
{
    unsigned long addr = 0;
    uint8_t u8;
    uint16_t u16;
    uint32_t u32;
    ndb_elem_ptr elp;
    int align = 1;

    if (ndb_has_prop_and_value(container, "data_type", "composite") == 0) {

        if (ndb_has_prop_and_value(container, "align", "false") == 0) {
            align = 0;
        }

        for_each_children(container, elp) {
            
            if (xmlNodeIsText(elp)) 
                continue;

            //printf("field: %s\n", ndb_get_name(elp));
            if (ndb_has_prop_and_value(elp, "data_type", "BYTE") == 0) {
                u8 = ndb_get_value_as_u8(elp);
                addr = fill_byte(buf, addr, u8);
                continue;
            }

            if (ndb_has_prop_and_value(elp, "data_type", "HALFWORD") == 0) {
                u16 = ndb_get_value_as_u16(elp);
                addr = fill_halfword(buf, addr, u16, align);
                continue;
            }

            if (ndb_has_prop_and_value(elp, "data_type", "WORD") == 0) {
                u32 = ndb_get_value_as_u32(elp);
                addr = fill_word(buf, addr, u32, align);
                continue;
            }

            /* default data_type: WORD*/
            u32 = ndb_get_value_as_u32(elp);
            addr = fill_word(buf, addr, u32, align);
        }
    }

    return buf;
}
